/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package grasp;

import model.Continente;
import model.Pais;
import model.ReservaAlmacen;
import model.Vuelo;
import model.Almacen;
import model.Pedido;
import model.VueloXRuta;
import model.Ruta;
import model.Fecha;
import java.io.* ;
import java.util.*;
import utils.Constantes;

/**
 *
 * @author walter
 */
public class Algoritmo {
    static ArrayList<Continente> arContinente = new ArrayList<Continente>();
    static ArrayList<Pais> arPais = new ArrayList<Pais>();
    static ArrayList<Pedido> arPedido = new ArrayList<Pedido>();
    static ArrayList<Almacen> arAlmacen = new ArrayList<Almacen>();
    static ArrayList<Vuelo> arVuelo = new ArrayList<Vuelo>();
    static ArrayList<VueloXRuta> arVueloXRuta = new ArrayList<VueloXRuta>(); //ver si es necesario
    static ArrayList<ReservaAlmacen> arReserva = new ArrayList<ReservaAlmacen>();
    Ruta ruta;
    Pedido pedido;
    
    public int getRangoHora( int serv ){
        serv = 3 ;
        int resp ;
        switch( serv ){
            case 0: resp = 8 ; break ;
            case 1: resp = 16 ; break ;
            case 2: resp = 24 ; break ;
            default: resp = 48 ; break ;
        }
        return resp ;
    }
    
    public double generateProb(){
        Random r = new Random() ;
//        System.out.println( "Random = " + r.nextDouble() ) ;
        return r.nextDouble() ;
    }
    
    
    public void print( ArrayList<Double> data ) throws IOException{
        FileWriter fw = new FileWriter( "OUTPUT/output.in" ) ;
        PrintWriter pw = new PrintWriter( fw ) ;
        
        if( data.isEmpty() ){
            pw.println( "No se encontró ruta" ) ;
        }
        
        String t[] = new String[ 4 ] ;
        t[ 0 ] = "Tiempo total " ;
        t[ 1 ] = "Costo total " ;
        t[ 2 ] = "Distancia total " ;
        
        for(int i = 0 ; i < data.size() ; i++){
            pw.println( t[ i ] + "= " + data.get( i ) ) ;
        }
        pw.close() ;
    }
    
    
    public int calcularMaxIt(){
        return 1000 ;
    }
    
    public double funObj(ArrayList<Vuelo> vuelos){
        double costo = 0;
        for(int i = 0; i < vuelos.size(); i++){
            costo += vuelos.get(i).costo;
        }
        return costo;
    }

    private int funDist(ArrayList<Vuelo> vuelos){
        int recorrido = 0 ;
        for (int i=0; i<vuelos.size(); i++){
            recorrido+=vuelos.get(i).recorrido;
        }
        return recorrido;
    }
    
    private boolean almacenDisponible(Almacen almacen, Fecha fechaInicio, Fecha fechaFin){
        boolean siDisponible = false;
        int cantReservas = 0;
        ReservaAlmacen reservaActual;
        
        /*Verificar disponibilidad del almacen entre fechaInicio y fechaFIn*/
        
        //Genero sumatoria de reservas del alamcen entre fInicio y fFin
        //Comparo esta sumatoria con su capacidad
        for (int i =0; i<arReserva.size();i++){
            reservaActual = arReserva.get(i);
            //if ((almacen == reservaActual.almacen && (fechaInicio.esMenorQue(reservaActual.fechaIngresoAprox) &&
            //        reservaActual.fechaSalidaAprox.esMenorQue(fechaFin))))
            if( almacen == reservaActual.almacen &&  
              ((fechaInicio.esMenorQue(reservaActual.fechaIngresoAprox) && reservaActual.fechaIngresoAprox.esMenorQue(fechaFin)) ||
               (fechaInicio.esMenorQue(reservaActual.fechaSalidaAprox)  && reservaActual.fechaSalidaAprox.esMenorQue(fechaFin))  ||
               (reservaActual.fechaIngresoAprox.esMenorQue(fechaInicio) && fechaFin.esMenorQue(reservaActual.fechaSalidaAprox))))
                cantReservas++;
        }
        if (cantReservas<almacen.capacidad){
            siDisponible=true;
        }
        
        return siDisponible;
    }
    
    private boolean verificarDisponibilidadAlmacenes(Vuelo vueloActual, Fecha fechaRegistroPedido,
            Almacen almacenDestino, ArrayList<Vuelo> solucion){
        boolean disponibleOrigen = false, disponibleDestino = false;
        Fecha horaInicioOrigen=null, horaFinOrigen = null, horaInicioDestino=null, horaFinDestino=null;
        Almacen almacenOrigen=vueloActual.origen;
            //Comparar que entre fechaRegistroPedido y vueloActual.fechaSalida, el almacen no esté planificado a llenarse
            //¿De donde saco la cantidad de espacio libre en el alamacen de hora1 a hora2?
            //Verificar que a la hora de vueloActual.fechaLlegada, el alamacen destino no esté lleno
        
        //Caso: sea un vuelo intermediario de la ruta
        if (solucion.size()>0 && vueloActual.destino != almacenDestino){
            
            Vuelo ultimoVuelo = solucion.get(solucion.size()-1);
            horaInicioOrigen = ultimoVuelo.fechaLlegada;
            horaFinOrigen = vueloActual.fechaSalida;
            horaInicioDestino = vueloActual.fechaLlegada;
            //Como aún no se sabe la hora de salida del siguiente vuelo:
            horaFinDestino = new Fecha(horaInicioDestino.dia, horaInicioDestino.mes,
                    horaInicioDestino.dia, horaInicioDestino.hora + 1);
            
        }
        
        //Caso: sea el primer vuelo de toda la ruta
        if (solucion.size() == 0){
            
            horaInicioOrigen = fechaRegistroPedido;
            horaFinOrigen = vueloActual.fechaSalida;
            horaInicioDestino = vueloActual.fechaLlegada;
            //Como aún no se sabe la hora de salida del siguiente vuelo:
            horaFinDestino = new Fecha(horaInicioDestino.dia, horaInicioDestino.mes,
                    horaInicioDestino.dia, horaInicioDestino.hora + 1);
                        
        }
        
        //Caso: sea un único vuelo toda la ruta
        if (vueloActual.destino == almacenDestino && solucion.size()==0){

            horaInicioOrigen = fechaRegistroPedido;
            horaFinOrigen = vueloActual.fechaSalida;
            horaInicioDestino = vueloActual.fechaLlegada;
            horaFinDestino = new Fecha(vueloActual.fechaLlegada.dia, vueloActual.fechaLlegada.mes, 
                    vueloActual.fechaLlegada.anho, vueloActual.fechaLlegada.hora + Constantes.TOLERANCIA);
        }
        
        //Caso: sea el último vuelo de varios vuelos
        if (vueloActual.destino == almacenDestino && solucion.size()>0){
            
            Vuelo ultimoVuelo = solucion.get(solucion.size()-1); 
            horaInicioOrigen = ultimoVuelo.fechaLlegada;
            horaFinOrigen = vueloActual.fechaSalida;
            horaInicioDestino = vueloActual.fechaLlegada;
            horaFinDestino = new Fecha(vueloActual.fechaLlegada.dia, vueloActual.fechaLlegada.mes, 
                    vueloActual.fechaLlegada.anho, vueloActual.fechaLlegada.hora + Constantes.TOLERANCIA);
 
        }
        
        //verificar que el almacen origen tenga disponibilidad hasta que salga el vuelo
        if (almacenDisponible(almacenOrigen, horaInicioOrigen, horaFinOrigen)){
            disponibleOrigen = true;
        }
            
        //verificar que el almacen destino tenga disponibilidad hasta que recojan el paquete
        if (almacenDisponible(almacenDestino, horaInicioDestino, horaFinDestino)){
            disponibleDestino = true;
        }
        
        return disponibleDestino && disponibleOrigen;
    }    
    
    private ArrayList<Vuelo> getCandidatos(Almacen origen, Fecha fechaRegistro, int continental, ArrayList<Vuelo> usados,
            ArrayList<Vuelo> solucion, Almacen almacenDestino){
        ArrayList<Vuelo> resp = new ArrayList<Vuelo>();
        Vuelo vueloActual;
        Fecha fechaBase;
        int diasComoMax = 1;

        if (continental == 1)
            diasComoMax = 2;
        
        for(int i = 0; i < arVuelo.size(); i++){
            vueloActual = arVuelo.get(i);
            
            if (solucion.size()==0){
                //Si es el primer vuelo
                fechaBase=fechaRegistro;
            }else{
                //Si es cualquier otro
                fechaBase = solucion.get(solucion.size()-1).fechaLlegada;
            }
            
            //que el almacen sea el origen
            //Se verifca que las fechas de salida de los vuelos sean despues de la fecha de registro
            //que ninguna fecha de llegada sobrepase las 24 o 48 horas desde la fecha de registro
            //que haya capacidad en el vuelo
            //que haya espacio en los alamcenes de origen y destino (falta, salvaje)
            //que no pertenezca al arreglo de vuelos usados
            
            if (vueloActual.origen.same(origen)){
                //System.out.print("Candidato #" +i+"::: ");
                if (fechaBase.esMenorQue(vueloActual.fechaSalida)){
                    //System.out.print("Fecha Salida: aceptada/");
                        if (vueloActual.fechaLlegada.esMenorQue(fechaRegistro.mas1o2Dias(diasComoMax))){
                            //System.out.print("Total de horas: aceptado/");
                            if (vueloActual.getStock() <= vueloActual.capacidad){
                                //System.out.print("Capacidad del vuelo: aceptado/");
                                if (verificarDisponibilidadAlmacenes(vueloActual, fechaRegistro,almacenDestino,solucion)){
                                    //System.out.print("Capacidad de almacenes: aceptado/");
                                        if (!usados.contains(vueloActual)){
                                            //System.out.print("No pertenece a usados\n");
                                            resp.add(vueloActual);
                                            //System.out.println("(ACEPTADO)"+ vueloActual.idVuelo);
                                        }else{
                                            //System.out.print("SÍ pertenece a usados\n");
                                            //System.out.println("(RECHAZADO)");
                                        }
                                }else;
                                    //System.out.print("Capacidad de almacenes: rechazada\n");
                            }else;
                                //System.out.print("Capacidad del vuelo: rechazada\n");
                        }else;
                            //System.out.print("Total de horas: rechazada (vueloActual)"+vueloActual.fechaLlegada.hora+"-"+vueloActual.fechaLlegada.mes+"/"+vueloActual.fechaLlegada.dia+"(fechaPedido)"+fechaRegistro.hora+"-"+fechaRegistro.mes+"/"+fechaRegistro.dia+"\n");
                }else;
                    //System.out.print("Fecha Salida: rechazada\n");
            }//else
//                System.out.println();
                
        }
        
        return resp;
    }

    private class CustomComparator implements Comparator<Vuelo>{

        @Override 
        public int compare(Vuelo o1, Vuelo o2) {
            int solucion=0;
            if (o1.costo < o2.costo){
                solucion = 1;
            }else if (o1.costo > o2.costo)
                solucion = -1;
            else if (o1.costo == o2.costo && o1.duracion < o2.duracion)
                solucion = 1;
            else if (o1.costo == o2.costo && o1.duracion > o2.duracion)
                solucion = -1;
            else if (o1.costo == o2.costo && o1.duracion == o2.duracion)
                solucion = 0;
            return solucion;
            //throw new UnsupportedOperationException("Not supported yet.");
        }
        
    }
    
    private ArrayList<Vuelo> ordenarCandidatos(ArrayList<Vuelo> arrV){
        Collections.sort(arrV, new CustomComparator());
        return arrV;
    }
    private ArrayList<Vuelo> cortarCandidatos(double alpha, ArrayList<Vuelo> arrV){

        ArrayList<Vuelo> rcl=null;
        
        if (arrV.size()>2){
            int newCandidatosSize = (int)(arrV.size()*alpha);
            List<Vuelo> auxVueloList;
            auxVueloList = arrV.subList(0, newCandidatosSize);
            rcl = new ArrayList<Vuelo>(auxVueloList);
        }else
            rcl = arrV;
        
        return rcl;
    }
    
    public Ruta generarRutaGrasp(Almacen origen, Almacen destino, Fecha fechaCreacion){

        int continental;
        double alpha = 0.45;
        int max_iteraciones = calcularMaxIt();
        ArrayList<Vuelo> solucionBest = new ArrayList<Vuelo>();
        ArrayList<Vuelo> rcl;
        Vuelo elegido;
        int aleatorio;
        int noExisteRuta=0;
        
        //Se evalúa si el viaje es intercontinental o no
        if(origen.pais.continente == destino.pais.continente)
            //No es intercontinental
            continental = 0;
        else
            //Sí es intercontinental
            continental = 1;

        //Para cada iteración
        for(int i = 0; i < max_iteraciones; i++){     
            //System.out.println("Solucion #1:");
            ArrayList<Vuelo> solucion = new ArrayList<Vuelo>();
            ArrayList<Vuelo> usados = new ArrayList<Vuelo>();
            
            //Se comienza a armar la solución posible que podría reemplazar a bestSolution
            //El bucle se detendrá cuando el último vuelo que forma parte de la solución posible tenga como destino
            //nuesto destino objetivo
            int contador = 0;
            while(solucion.isEmpty() || solucion.get(solucion.size() - 1).destino.diff(destino) ){
                contador++;
                //System.out.println("Vuelo #: " + contador);
                ArrayList<Vuelo> candidatos = new ArrayList<Vuelo>();
                //Se arma la lista de vuelos candidatos
                if (solucion.isEmpty())
                    //si la posible solucion aun esta vacía, se tomará origen
                    candidatos = getCandidatos(origen, fechaCreacion, continental,usados,solucion, destino);
                else
                    //si la posible solucion esta con datos, se obtiene el ultimo vuelo y se toma su destino como nuestro origen
                    candidatos = getCandidatos(solucion.get(solucion.size() - 1).destino, fechaCreacion, continental,
                            usados,solucion,destino);
                //Ahora se debe verificar si los candidatos son nulos
                if (candidatos.size() == 0){
                    //Si son nulos, se realizará un backtracking y se forzará un continue
                    //{BACKTRACKING: debes tener tu listaSolucion y listaVisitados, solo se borrara la opcion de la solucion,
                    //mas no de los visitados}
                    if (solucion.size()>0){
                        Vuelo vueloAEliminar;
                        vueloAEliminar = solucion.get(solucion.size()-1);
                        solucion.remove(solucion.size()-1);
                        //System.out.println("[!!!!]No se encontró vuelo, se volverá a un vuelo anterio. Vuelo eliminado: "+vueloAEliminar.idVuelo);
                        continue;
                    }else if (solucion.size()==0){
                        //Este caso ocurre cuando el origen nunca encontro ni un posible candidato.
                        noExisteRuta=1;
                        break;
                    }
                        
                    
                }

                //Se ordenará la lista de candidatos
                ordenarCandidatos(candidatos);
                //Se relajará la voracidad
                rcl = cortarCandidatos(alpha, candidatos);
                //Se toma uno al azar del rcl
                aleatorio = (int)(Math.random() * rcl.size());
                elegido = rcl.get(aleatorio);
                //Se agrega el elegido a la solucion y a los usados
                solucion.add(elegido);
                usados.add(elegido);
                //System.out.println("[!!!!]Sí se encontro vuelo ("+elegido.idVuelo+")");
            }

            //Busca salir del bucle de iteraciones, ya que el origen no encuentra ruta posible
            if (noExisteRuta == 1){
                //System.out.println("[][][]El origen no encontró ninguna ruta posible");
                break;
            }
            
            if(solucionBest.isEmpty() || 
                    (funObj(solucion) < funObj(solucionBest)) || 
                    ( (funObj(solucion) == funObj(solucionBest)) && (funDist(solucion) < funDist(solucionBest))) ) 
                solucionBest = solucion;
        }
        
        Ruta rutaG=null;
        if(!solucionBest.isEmpty()){
            
//            System.out.println("*******************************Se encontró ruta!");
            rutaG = new Ruta(origen, destino);
            Vuelo vueloAnteriorSF;
            rutaG.fechaSalida = (Fecha)solucionBest.get(0).fechaSalida.clone();
            rutaG.fechaLlegada = (Fecha)solucionBest.get(solucionBest.size()-1).fechaLlegada.clone();

            for(int i = 0; i < solucionBest.size(); i++){
                arVueloXRuta.add(new VueloXRuta(rutaG, solucionBest.get(i)));

                //Si es el primer vuelo
                if(i == 0){
                    arReserva.add(new ReservaAlmacen(origen, null, fechaCreacion, solucionBest.get(i).fechaSalida, null, null, 0));
                }
                //Si es el último vuelo
                else if(i == solucionBest.size() - 1){
                    Vuelo vueloFinal = solucionBest.get(solucionBest.size() - 1);
                    Fecha fechaMaxRecojo = new Fecha(vueloFinal.fechaLlegada.dia, vueloFinal.fechaLlegada.mes, 
                        vueloFinal.fechaLlegada.anho, vueloFinal.fechaLlegada.hora + Constantes.TOLERANCIA);

                    arReserva.add(new ReservaAlmacen(vueloFinal.destino, null, vueloFinal.fechaLlegada, fechaMaxRecojo,null,null,0));

                    /*else if(continental == 1)
                        arReserva.add(new ReservaAlmacen(solucionBest.get(solucionBest.size() - 1).destino, null, solucionBest.get(solucionBest.size() - 1).fechaSalida, fechaCreacion.mas1o2Dias(2), null, null, 0));
                        * */
                }
                //Si es cualquier otro vuelo
                else{
                    //El almacem será el almacen destino del vuelo anterior
                    vueloAnteriorSF = solucionBest.get(i-1);
                    arReserva.add(new ReservaAlmacen(vueloAnteriorSF.destino, null, vueloAnteriorSF.fechaLlegada, solucionBest.get(i).fechaSalida, null, null, 0));
                }
            }
            
            //Ahora lleno los vuelos de la ruta
            for (int i=0; i<solucionBest.size(); i++){
                rutaG.vuelos.add(solucionBest.get(i));
            }
        }

        return rutaG;
    }
}
